package com.testing.sunnycommon.automation;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.testing.sunnycommon.automation.annotations.KeyDescription;
import com.testing.sunnycommon.automation.annotations.KeyParams;
import com.testing.sunnycommon.automation.annotations.KeyUsage;
import com.testing.sunnycommon.automation.model.ApiResult;
import com.testing.sunnycommon.automation.model.AutoResult;
import com.testing.sunnycommon.model.SunnyVariable;
import com.testing.sunnycommon.service.SunnyVariableService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Classname ApiKeywords
 * @Description 类型说明
 * @Date 2023/5/22 20:30
 * @Created by 特斯汀Roy
 */
@Slf4j
@Component
public class ApiKeywords {

    @Resource
    SunnyVariableService sunnyVariableService;

    private CloseableHttpClient client = HttpClients.createDefault();
    //仅保留上一次的执行结果，用做之后的断言
    private ApiResult apiResult = new ApiResult();

    public ApiResult getApiResult() {
        return apiResult;
    }

    //用来保存头域的map
    private Map<String, String> headerMap = new HashMap<>();
    // 是否使用头域的开关量
    private boolean isUseHeader = true;

    //是否使用cookie的开关量
    private boolean isUseCookie = true;
    //创建一个cookieStore对象，用于存储cookie，当发送请求的时候，
    // 每次决定的是client是否携带这个cookieStore，从而控制是否携带cookie
    private CookieStore publicCookieStore = new BasicCookieStore();


    @KeyUsage("添加请求头")
    @KeyDescription("在接口测试过程中添加请求头，并打开使用请求头开关，在之后的请求过程中，使用请求头")
    public AutoResult addHeader(@KeyParams(name = "headerName", value = "请求头键名") String headerName, @KeyParams(name = "headerValue", value = "请求头值") String headerValue) {
        headerMap.put(headerName, headerValue);
        isUseHeader = true;
        log.info("向接口头域池中插入头域" + headerName + "：" + headerValue + "成功");
        return AutoResult.pass("向接口头域池中插入头域" + headerName + "：" + headerValue + "成功");
    }

    @KeyUsage("使用请求头")
    @KeyDescription("设置之后接口使用已经存储的请求头")
    public AutoResult useHeader() {
        isUseHeader = true;
        return AutoResult.pass("切换使用头域");
    }

    @KeyUsage("不使用请求头")
    @KeyDescription("设置之后接口不再使用已经存储的请求头")
    public AutoResult notUseHeader() {
        isUseHeader = false;
        return AutoResult.pass("切换不使用头域");
    }

    @KeyUsage("清空头域池")
    @KeyDescription("清空头域池的同时，关闭使用请求头开关，设置之后接口不再使用已经存储的请求头")
    public AutoResult clearHeaders() {
        headerMap.clear();
        isUseHeader = false;
        return AutoResult.pass("清空存储的头域");
    }

    @KeyUsage("打开使用cookie的开关")
    @KeyDescription("打开使用cookie的开关,之后的请求会带上存储好的cookie")
    public AutoResult useCookie() {
        isUseCookie = true;
        return AutoResult.pass("打开使用cookie的开关");
    }

    @KeyUsage("关闭使用cookie的开关")
    @KeyDescription("关闭使用cookie的开关,之后的请求不再携带存储的cookie")
    public AutoResult notUseCookie() {
        isUseCookie = false;
        return AutoResult.pass("关闭使用cookie的开关");
    }

    @KeyUsage("清空测试过程中的cookie")
    @KeyDescription("清空测试过程中的cookie，非必要无需使用该方法，控制是否使用cookie即可")
    public AutoResult clearCookie() {
        //重新实例化cookieStore，实现清空
        publicCookieStore = new BasicCookieStore();
        return AutoResult.pass("清空测试过程中存储的cookie");
    }


    //创建client实例对象
    private void createClient() {
        //让client在创建的时候，决定是否要携带公用的cookieStore
        if (isUseCookie) {
            //如果设置使用cookie，那么创建的时候，设置cookieStore为公用的cookie
            this.client = HttpClients.custom().setDefaultCookieStore(publicCookieStore).build();
        } else {
            //否则创建client不带cookieStore
            this.client = HttpClients.custom().build();
        }
    }

    /***************************请求方法按照put和post进行划分，以及get和delete进行划分******************************/
    @KeyUsage("测试get请求")
    @KeyDescription("测试get请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testGet(@KeyParams(name = "url", value = "接口url地址") String url) {
        try {
            HttpGet get = new HttpGet(url);
            sendRequest(get);
        } catch (Exception e) {
            log.error("get请求创建失败", e.fillInStackTrace());
            apiResult.setStatusCode(-1);
            apiResult.setResponseText(e.fillInStackTrace().toString());
        }
        return judgeRequestResult();
    }

    @KeyUsage("测试delete请求")
    @KeyDescription("测试delete请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testDelete(@KeyParams(name = "url", value = "接口url地址") String url) {

        try {
            HttpDelete delete = new HttpDelete(url);
            sendRequest(delete);
        } catch (Exception e) {
            log.error("delete请求创建失败", e.fillInStackTrace());
            apiResult.setStatusCode(-1);
            apiResult.setResponseText(e.fillInStackTrace().toString());
        }
        return judgeRequestResult();
    }


    @KeyUsage("测试urlencoded格式请求体的post请求")
    @KeyDescription("测试请求体格式为urlencoded格式的post请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testPostUrl(@KeyParams(name = "url", value = "请求url地址") String url, @KeyParams(name = "requestBody", value = "拼接好的url键值对格式字符串请求体") String requestBody) {
        doPostOrPut("post", url, "url", requestBody);
        return judgeRequestResult();
    }

    @KeyUsage("测试json格式请求体的post请求")
    @KeyDescription("测试请求体格式为json格式的post请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testPostJson(@KeyParams(name = "url", value = "请求url地址") String url, @KeyParams(name = "requestBody", value = "json格式请求体内容") String requestBody) {
        doPostOrPut("post", url, "json", requestBody);
        return judgeRequestResult();
    }

    @KeyUsage("测试文件格式请求体的post请求")
    @KeyDescription("测试请求体格式为文件格式的post请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testPostFile(@KeyParams(name = "url", value = "请求url地址") String url, @KeyParams(name = "requestParams", value = "文件格式请求体，用json格式进行拼接，" +
            "\\n 格式为：{\"text\":{\"文本格式参数1\":\"文本格式值1\",\"文本格式参数2\":\"文本格式值2\"},\"file\":{\"文件格式参数1\":\"文件路径1\"} ") String requestParams) {
        doPostOrPut("post", url, "file", requestParams);
        return judgeRequestResult();
    }


    @KeyUsage("测试json格式请求体的put请求")
    @KeyDescription("测试请求体格式为json格式的put请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testPutJson(@KeyParams(name = "url", value = "请求url地址") String url, @KeyParams(name = "requestBody", value = "json格式请求体内容") String requestBody) {
        doPostOrPut("put", url, "json", requestBody);
        return judgeRequestResult();
    }

    @KeyUsage("测试urlencoded格式请求体的post请求")
    @KeyDescription("测试请求体格式为urlencoded格式的post请求，并存储返回结果的状态码、返回头、返回体到内存中，进行后续断言操作，返回请求是否成功的结果和返回体信息")
    public AutoResult testPutUrl(@KeyParams(name = "url", value = "请求url地址") String url, @KeyParams(name = "requestBody", value = "拼接好的url键值对格式字符串请求体") String requestBody) {
        doPostOrPut("put", url, "url", requestBody);
        return judgeRequestResult();
    }

    /**
     * 有请求体的请求方法封装
     *
     * @param type
     * @param url
     * @param requestBody
     */
    private void doPostOrPut(String type, String url, String contentType, String requestBody) {
        HttpEntityEnclosingRequestBase httpMethod;
        try {
            switch (type) {
                case "post":
                default:
                    httpMethod = new HttpPost(url);
                    break;
                case "put":
                    httpMethod = new HttpPut(url);
                    break;
            }
        } catch (Exception e) {
            log.error("请求创建失败", e.fillInStackTrace());
            apiResult.setStatusCode(-1);
            apiResult.setResponseText(e.fillInStackTrace().toString());
            return;
        }
        //设置请求体
        HttpEntity entity;
        switch (contentType) {
            case "url":
                entity = new StringEntity(requestBody, "utf-8");
                //设置请求头中的content-type字段
                httpMethod.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
                break;
            case "json":
            default:
                entity = new StringEntity(requestBody, "utf-8");
                httpMethod.setHeader("Content-Type", "application/json;charset=utf-8");
                break;
            case "file":
                //将文件上传的参数，以json格式来进行传递，{"text":{"id":"WU_FILE_0","size":"11988","type":"image/png"},"file":{"file":"E:\QSwork\素材\图片素材\Jmeter标题2.jpg"}}
                // 但是由于编写代码的时候，文件格式和文本格式所使用的方法是不同的，所以封装的时候，把参数进行一下区分
                MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
                //解析一下json格式的参数，其中text里的内容，加到textBody
                try {
                    //获取text和file的json对象，并且遍历他们
                    JSONObject jsonObject = JSONObject.parseObject(requestBody);
                    JSONObject text = jsonObject.getJSONObject("text");
                    for (String textKey : text.keySet()) {
                        //将json中键为textKey，和它对应的字符串类型值，加到textBody里
                        multipartEntityBuilder.addTextBody(textKey, text.getString(textKey));
                    }
                    JSONObject file = jsonObject.getJSONObject("file");
                    for (String fileKey : file.keySet()) {
                        //file里面的内容，加到fileBody
                        multipartEntityBuilder.addBinaryBody(fileKey, new File(file.getString(fileKey)));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("对文件格式参数进行解析处理失败", e.fillInStackTrace());
                    apiResult.setStatusCode(-1);
                    apiResult.setResponseText(e.fillInStackTrace().toString());
                    return;
                }
                entity = multipartEntityBuilder.build();
                //请求头不要再去设置content-type了
                //                httpMethod.setHeader("Content-Type","multipart/form-data; boundary=----WebKitFormBoundaryMVKkXDG2H71s1zl8");
                break;
            case "xml":
                entity = new StringEntity(requestBody, "utf-8");
                //设置请求头中的content-type字段
                httpMethod.setHeader("Content-Type", "text/xml;charset=utf-8");
                break;
        }
        //设置请求体格式
        httpMethod.setEntity(entity);
        sendRequest(httpMethod);
    }

    /**
     * 完成请求发送以及返回体解析的方法
     *
     * @param httpMethod 传入已完成拼接的http请求报文对象
     */
    private void sendRequest(HttpRequestBase httpMethod) {
        //createClient方法中已经决定了是否使用cookie
        createClient();
        //设置请求发起连接和等待返回的超时时间，均为10秒，如果有特殊需要，可以根据具体测试场景来调整，默认是
        httpMethod.setConfig(RequestConfig.custom().setConnectTimeout(10000).setSocketTimeout(10000).build());
        try {
            if (isUseHeader) {
                //遍历每个头域插入到请求中
                for (String headerKey : headerMap.keySet()) {
                    httpMethod.setHeader(headerKey, headerMap.get(headerKey));
                }
            }
            CloseableHttpResponse response = client.execute(httpMethod);
            apiResult.setStatusCode(response.getStatusLine().getStatusCode());
            apiResult.setResponseHeaders(response.getAllHeaders());
            //解析返回体
            HttpEntity responseEntity = response.getEntity();
            //把返回体中的文字信息处理为字符串
            String responseText = EntityUtils.toString(responseEntity, "utf-8");
            apiResult.setResponseText(responseText);
            log.info("最终的返回结果是" + apiResult);
        } catch (IOException e) {
            //对于异常的处理： 将异常信息作为返回结果进行标识
            apiResult.setStatusCode(-1);
            apiResult.setResponseText(e.fillInStackTrace().toString());
        }
    }

    /**
     * @return
     */
    private AutoResult judgeRequestResult() {
        if (apiResult.getStatusCode() == -1) {
            log.error("接口测试执行失败:" + apiResult.getResponseText());
            return AutoResult.fail("接口测试执行失败:" + apiResult.getResponseText(), "");
        } else {
            log.info("接口请求执行成功，返回结果是：" + apiResult.getResponseText());
            return AutoResult.pass("接口请求执行成功，返回结果是：" + apiResult.getResponseText());
        }
    }


    /*******************************断言方法********************************/

    @KeyUsage("断言返回结果的状态码")
    @KeyDescription("提供一个状态码，断言实际返回结果是否为这个状态")
    public AutoResult assertStatusCode(@KeyParams(name = "expectedCode", value = "预期的状态码") String expectedCode) {
        if (apiResult.getStatusCode() == -1) {
            return AutoResult.skipped("接口请求已经失败了，跳过断言步骤");
        }
        try {
            int expectedCodeNumber = Integer.parseInt(expectedCode);
            if (apiResult.getStatusCode() == expectedCodeNumber) {
                log.info("断言状态码为" + expectedCode + "成功");
                return AutoResult.pass("断言状态码为" + expectedCode + "成功");
            } else {
                log.error("实际状态码为" + apiResult.getStatusCode() + "而不是" + expectedCode + "，测试失败");
                return AutoResult.fail("实际状态码为" + apiResult.getStatusCode() + "而不是" + expectedCode + "，测试失败", "");
            }
        } catch (NumberFormatException exception) {
            log.error("状态码必须是数字", exception.fillInStackTrace());
            return AutoResult.fail("状态码必须是数字而不是" + expectedCode, "");
        }
    }

    @KeyUsage("断言返回体通过jsonPath解析后包含预期内容")
    @KeyDescription("断言返回体通过jsonPath解析后包含预期内容")
    public AutoResult assertResponseJson(@KeyParams(name = "jsonPath", value = "解析返回体所使用的jsonPath表达式") String jsonPath, @KeyParams(name = "expectedResult", value = "预期解析结果中包含的内容") String expectedResult) {
        if (apiResult.getStatusCode() == -1) {
            return AutoResult.skipped("接口请求已经失败了，跳过断言步骤");
        }
        try {
            String responseText = apiResult.getResponseText();
            String actualResult = JSONPath.read(responseText, jsonPath).toString();
            if (actualResult.contains(expectedResult)) {
                log.info("解析返回体json的结果是" + actualResult + "包含" + expectedResult + "，断言成功");
                return AutoResult.pass("解析返回体json的结果是" + actualResult + "包含" + expectedResult + "，断言成功");
            } else {
                log.error("解析返回体json的结果是" + actualResult + "不包含" + expectedResult + "，断言失败");
                return AutoResult.fail("解析返回体json的结果是" + actualResult + "不包含" + expectedResult + "，断言失败", "");
            }
        } catch (Exception e) {
            log.error("解析返回体失败，返回体的实际内容是" + apiResult.getResponseText(), e.fillInStackTrace());
            return AutoResult.fail("解析返回体失败，返回体的实际内容是" + apiResult.getResponseText(), "");
        }


    }


    @KeyUsage("断言返回体通过正则表达式解析后包含预期内容")
    @KeyDescription("断言返回体通过正则表达式解析后包含预期内容")
    public AutoResult assertResponseRegex(@KeyParams(name = "regex", value = "提供一个正则表达式，然后从其中来基于 限制条件(.*)限制条件 的格式来匹配第一个分组中的内容作为解析结果") String regex, @KeyParams(name = "expectedResult", value = "预期解析结果中包含的内容") String expectedResult) {
        if (apiResult.getStatusCode() == -1) {
            return AutoResult.skipped("接口请求已经失败了，跳过断言步骤");
        }
        try {
            String responseText = apiResult.getResponseText();
            Pattern p = Pattern.compile(regex);
            Matcher matcher = p.matcher(responseText);
            if (matcher.find()) {
                String actualResult = matcher.group(1);
                if (actualResult.contains(expectedResult)) {
                    log.info("正则解析返回体的结果是" + actualResult + "包含" + expectedResult + "，断言成功");
                    return AutoResult.pass("解析返回体json的结果是" + actualResult + "包含" + expectedResult + "，断言成功");
                } else {
                    log.error("正则解析返回体的结果是" + actualResult + "不包含" + expectedResult + "，断言失败");
                    return AutoResult.fail("正则解析返回体的结果是" + actualResult + "不包含" + expectedResult + "，断言失败", "");
                }
            } else {
                log.error("正则表达式没有解析到结果，请检查正则表达式" + regex + "，返回结果是" + responseText);
                return AutoResult.fail("正则表达式没有解析到结果，请检查正则表达式" + regex + "，返回结果是" + responseText, "");
            }

        } catch (Exception e) {
            log.error("解析返回体失败，返回体的实际内容是" + apiResult.getResponseText(), e.fillInStackTrace());
            return AutoResult.fail("解析返回体失败，返回体的实际内容是" + apiResult.getResponseText(), "");
        }
    }

    /*********************************参数操作********************************************/
    @KeyUsage("在自动化执行过程中存储参数")
    @KeyDescription("在自动化执行过程中存储参数")
    public AutoResult saveVariable(String varName, String varValue) {
        //创建好参数对象
        SunnyVariable variable = new SunnyVariable();
        variable.setId(0L);
        variable.setProjectId(1L);
        variable.setVarName(varName);
        variable.setVarValue(varValue);
        boolean result = sunnyVariableService.saveOrUpdateVariable(variable);
        log.info("存储参数成功，存储的参数是  " + varName + "：" + varValue);
        return result ? AutoResult.pass("存储参数成功，存储的参数是  " + varName + "：" + varValue) : AutoResult.fail("存储参数失败，请检查是否传入了不合适的id", "");
    }


    @KeyUsage("基于头域名称，获取头域的值，然后存储为变量")
    @KeyDescription("基于头域名称，获取头域的值，然后存储为变量")
    public AutoResult saveResponseHeaderVar(@KeyParams(name = "varName", value = "要存储的变量的名字") String varName,
                                            @KeyParams(name = "headerName", value = "要从获取哪个头的值存到变量里") String headerName) {
        String value = "";
        for (Header responseHeader : apiResult.getResponseHeaders()) {
            if (responseHeader.getName().equals(headerName)) {
                value = responseHeader.getValue();
            }
        }
        saveVariable(varName, value);
        log.info("存储变量" + varName + "的值为" + value);
        return AutoResult.pass("存储变量" + varName + "的值为" + value);
    }

    @KeyUsage("通过JsonPath解析返回体中的内容，存储为环境变量")
    @KeyDescription("基于JsonPath解析请求得到的返回体中的内容，存储为环境变量")
    public AutoResult saveResponseJsonVar(@KeyParams(name = "varName", value = "要存储的变量的名字") String varName,
                                          @KeyParams(name = "jsonPath", value = "通过jsonPath解析返回体中的内容获取值，作为变量的值") String jsonPath) {
        String value = JSONPath.read(apiResult.getResponseText(), jsonPath).toString();
        saveVariable(varName, value);
        log.info("存储变量" + varName + "的值为" + value);
        return AutoResult.pass("存储变量" + varName + "的值为" + value);
    }
}
